﻿//#define __sourceHook

namespace JoinBox.WPF;

using JoinBox.Basal;
using System;
using System.Diagnostics;
using System.Threading;
using System.Windows.Interop;
using System.Windows.Threading;
using UserControl = System.Windows.Controls.UserControl;

/// <summary>
/// 嵌入WPF到Win32窗体
/// </summary>
public class WPFFrame : IDisposable
{
    #region 成员
    /// <summary>
    /// 在 Win32 窗口中呈现 WPF 内容 (WPF的嵌入方式)<br/>
    /// <see cref="WindowsAPI.SetParent"/> net35不能用,net35+可以用;而为了统一,都可以使用本函数<br/>
    /// <a href="https://docs.microsoft.com/zh-cn/dotnet/desktop/wpf/advanced/walkthrough-hosting-wpf-content-in-win32?view=netframeworkdesktop-4.8">技术链接</a>
    /// </summary>
    /// <![CDATA[
    /// net35+可以用SetParent,例子:
    /// //WPF把用户控件加入到新建窗体中,然后嵌入到cad主窗口
    /// System.Windows.Window win = new()
    /// {
    ///     Content = docwindow
    /// };
    /// //win.WindowStyle = windowstyle.None;//这个也有边
    /// //win.windowStyle = WindowStyle.Toolwindow;
    /// //win.windowStyle = windowStyle. ThreeDBorderwindow;
    /// //win.windowStyle = windowStyle.singleBorderwindow;win.Show();
    /// (int x, int y, int w, int h) = AnchorHelper.GetSizeInfo(_WPFFrame.Handle);
    /// win.Left = x; win.Top = y; win.Width = w; win.Height = h;
    /// //这两种取WPF句柄都可以
    /// //var handle = ((HindSource)PresentationSource. FromVisual(win)).Handle;
    /// var handle = new WindowInteropHelper(win).Handle;
    /// SetParent(handle, Acap.MainWindow.Handle);
    /// ]]>
    HwndSource? _sourcea;
    HwndSourceParameters _sourceaInfo;

    // 20220906 关闭的时候触发/拉伸窗口也会触发,因为跨线程处理了WPF内容,要写设置 WPFDispatcher
    // 引发的异常:“System.InvalidOperationException”(位于 WindowsBase.dll 中)
    public Dispatcher? WPFDispatcher;

    public string Name
    {
        set => _sourceaInfo.WindowName = value;
        get => _sourceaInfo.WindowName;
    }

    /// <summary>
    /// 句柄<br/>
    /// 就是<see cref="_sourceaInfo.ParentWindow"/>句柄<br/>
    /// 也就是Acap.MainWindow<br/>
    /// </summary>
    public IntPtr Handle
    {
        get
        {
            if (_sourcea != null)
                return _sourcea.Handle;
            return IntPtr.Zero;
        }
    }
    #endregion

    #region 构造
    WPFFrame(string? newWindowName = null)
    {
        newWindowName ??= new Guid().ToString();
        _sourceaInfo = new(newWindowName);
    }
    /// <summary>
    /// 嵌入WPF到Win32窗体
    /// </summary>
    /// <param name="parentWindow">要嵌入的父窗口句柄</param>
    /// <param name="newWindowName">新窗体名称</param>
    public WPFFrame(IntPtr parentWindow,
                    string? newWindowName = null) : this(newWindowName)
    {
        // 设置嵌入窗体
        _sourceaInfo.ParentWindow = parentWindow;
        // 设置嵌入窗体的样式
        _sourceaInfo.WindowStyle = (int)WS.WS_CHILD | (int)WS.WS_VISIBLE;
        _sourceaInfo.ExtendedWindowStyle = (int)WS.WS_EX_TOPMOST;
    }
    #endregion

    #region 方法
    /// <summary>
    /// 嵌入控件
    /// </summary>
    /// <param name="docWindow">来源的控件</param>
    public void Build(UserControl docWindow)
    {
        // 这里才new就不会受到构造函数的线程影响,是个跨线程对象
        _sourcea = new(_sourceaInfo)
        {
            RootVisual = docWindow
        };
        WPFDispatcher = Dispatcher.CurrentDispatcher;

        // 这里的句柄就是相当于win32窗体的句柄了
        // Handle = _hwndSourcea.CreateHandleRef().Handle;

#if __sourceHook
        // 加入事件
        if (_sourceHook is not null)
            _hwndSourcea.AddHook(Handlwndproc);
        _hwndSourcea.AutoResized += HwndSourcea_AutoResized;
#endif
    }

#if __sourceHook
    /// <summary>
    /// 事件
    /// </summary>
    public HwndSourceHook? _sourceHook;

    /// <summary>
    /// 自动调整大小时候发生
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void HwndSourcea_AutoResized(object sender, System.Windows.AutoResizedEventArgs e)
    {
    }

    /// <summary>
    /// WPF事件
    /// </summary>
    /// <param name="hwnd"></param>
    /// <param name="msg"></param>
    /// <param name="wParam"></param>
    /// <param name="lParam"></param>
    /// <param name="handled"></param>
    /// <returns></returns>
    public static IntPtr Handlwndproc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
    {
        switch (msg)
        {
            case (int)WM.WM_CREATE:
                Debug.WriteLine("创建成功");
                break;

            case (int)WM.WM_PAINT:
                // RECT r;
                // WindowsAPI.GetClientRect(_DM_Handle, out r);
                // WindowsAPI.SetWindowPos(hwnd, new IntPtr(-1),
                //    r.Left, r.Top, r.Right - r.Left, r.Bottom - r.Top, 0);
                // WindowsAPI.SetForegroundWindow(hwnd);
                break;
            default:
                break;
        }
        return IntPtr.Zero;
    }
#endif

    /// <summary>
    /// 设置WPF窗口尺寸
    /// </summary>
    /// <param name="x"></param>
    /// <param name="y"></param>
    /// <param name="width"></param>
    /// <param name="height"></param>
    public void SetRect(int x, int y, int width, int height)
    {
        _sourceaInfo.SetPosition(x, y);
        _sourceaInfo.SetSize(width, height);
    }

    /// <summary>
    /// 设置背景色/前置窗口触发刷新
    /// </summary>
    /// <param name="backgroundColor">背景色</param>
    public void SetWindowPos(System.Windows.Media.Color? backgroundColor = null)
    {
        if (_sourcea is null || Handle == IntPtr.Zero)
            return;

        WPFDispatcher?.BeginInvoke(() => {
            backgroundColor ??= System.Windows.Media.Color.FromRgb(255, 255, 255);// 纯白
            _sourcea.CompositionTarget.BackgroundColor = backgroundColor.Value;

            WindowsAPI.SetWindowPos(Handle); // 显示状态,会触发刷新
        });

        // UpdateWindow(Handle);// 显示窗体
        // Application.DoEvents();// winform界面处理消息队列,否则会令界面卡黑色边
    }
    #endregion

    void BeginInvoke(Action action)
    {
        if (_sourcea == null)
            return;
        if (_sourcea.Dispatcher.Thread.ThreadState != System.Threading.ThreadState.Running)
        {
            _sourcea = null;
            return;
        }
        _sourcea.Dispatcher.BeginInvoke(action);
    }

    #region IDisposable接口相关函数
    public bool IsDisposed { get; private set; } = false;

    /// <summary>
    /// 手动调用释放
    /// </summary>
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    /// <summary>
    /// 析构函数调用释放
    /// </summary>
    ~WPFFrame()
    {
        Dispose(false);
    }

    protected virtual void Dispose(bool disposing)
    {
        // 不重复释放,并设置已经释放
        if (IsDisposed) return;
        IsDisposed = true;

        // 移除WPF,调用WPF内部委托实现
        BeginInvoke(() => {
#if __sourceHook
        if (_sourceHook is not null)
            _hwndSourcea?.RemoveHook(_sourceHook);
#endif
            _sourcea?.Dispose();
        });

        try
        {
            // 如果线程终止异常,那么将导致退出函数后续都将终止
            if (WPFDispatcher == null || WPFDispatcher.Thread.ThreadState != System.Threading.ThreadState.Running)
            {
                WPFDispatcher = null;
                return;
            }
            WPFDispatcher.Thread.Abort();// 终止线程
            WPFDispatcher = null;
        }
        catch (Exception e)
        {
            Debug.WriteLine("线程终止异常::" + e);
        }
    }
    #endregion
}